// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: daniel

#pragma once

#include <array>
#include <deque>
#include <iostream>
#include <memory>
#include <set>
#include <string>
#include <tuple>
#include <unordered_map>
#include <utility>

#include <boost/asio/io_context.hpp>
#include <boost/asio/ssl/context.hpp>
#include <boost/asio/steady_timer.hpp>
#include <glog/logging.h>

#include "coin2/base/request/http2/asio_context.h"
#include "coin2/base/request/http2/connection.h"
#include "coin2/base/time.h"
#include "nghttp2/includes/nghttp2/asio_http2.h"
#include "nghttp2/includes/nghttp2/asio_http2_client.h"

namespace coin2::base::request::http2 {

//
// class Session
//
class Session {
  //
  // class RequstIdGenerator
  //
  class RequstIdGenerator {
   public:
    RequstIdGenerator() : req_id_{GetCurrentTimestamp() / 1000} {}
    int64_t Get() { return ++req_id_; }

   private:
    int64_t req_id_;
  };

 public:
  using Http2Connection = coin2::base::request::http2::Connection;
  using Http2AsioContext = coin2::base::request::http2::AsioContext;
  using RequestType = Http2AsioContext::RequestType;
  using CallbackType = Http2AsioContext::CallbackType;

 public:
  explicit Session(
      boost::asio::io_context* io_ctx,
      boost::asio::ssl::context* tls_ctx,
      const std::string& host,
      int port,
      const std::string& bind_ip);
  ~Session() = default;
  Session(const Session&) = delete;
  Session& operator=(const Session&) = delete;

  void AsyncRequest(RequestType req, CallbackType callback, int priority_weight);
  void AsyncSend(std::weak_ptr<Http2AsioContext> context);

  int ProcessTimeout(int64_t timeout);

 private:
  void Init();
  auto AllocConnection() -> std::shared_ptr<Http2Connection>;
  void OnTimer(const boost::system::error_code& ec);
  void SendImpl();
  void ProcessRequest(
      std::shared_ptr<Http2AsioContext> context,
      const boost::system::error_code& ec);
  auto CurrentConnection(int64_t ts) -> std::shared_ptr<Http2Connection>&;

 private:
  boost::asio::io_context* io_ctx_;
  boost::asio::ssl::context* tls_ctx_;
  const std::string host_;
  const int port_;
  const std::string bind_ip_;

  boost::asio::steady_timer timer_;
  RequstIdGenerator req_id_generator_;
  int current_conn_index_ = 0;
  std::array<std::shared_ptr<Http2Connection>, 2> connections_;
  std::set<std::shared_ptr<Http2Connection>> suspend_conns_;

  bool is_sending_ = false;
  std::unordered_map<int64_t, std::shared_ptr<Http2AsioContext>> pending_reqs_;
  std::deque<int64_t> req_queue_;

  int64_t send_fail_tolerance_ = 0;
  int64_t max_requests_limit_ = 0;
  const int64_t timer_interval_sec_ = 5;  // 5 seconds
};

}  // namespace coin2::base::request::http2
